package com.yhsimon.realm.member.service.impl;

import com.alibaba.fastjson.JSON;
import com.yhsimon.realm.member.dto.user.BeanSeckillDto;
import com.yhsimon.realm.member.entity.user.SeckillBeanActivityEntity;
import com.yhsimon.realm.member.service.SeckillService;
import com.yhsimon.realm.member.utils.R;
import com.yhsimon.realm.member.vo.user.SeckillBeanActivityVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
public class SeckillBeanServiceImpl implements SeckillService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    private static final String SECKILL_BEAN_SESSION_CACHE_PREFIX = "seckill:bean:session:";

    private static final String SECKILL_BEAN_RECORD_CACHE_PREFIX = "seckill:bean:record:";

    private static final String SECKILL_BEAN_CACHE_PREFIX = "seckill:bean:";


    @Override
    public R seckillBean(Long userId, Long seckillCount) {
        String sessionName = LocalDate.now() + "-" + LocalDateTime.now().getHour();
        String jsonStr = redisTemplate.opsForValue().get(SECKILL_BEAN_SESSION_CACHE_PREFIX + sessionName);
        if (!StringUtils.isEmpty(jsonStr)) {
            SeckillBeanActivityEntity seckillBeanActivityEntity = JSON.parseObject(jsonStr, SeckillBeanActivityEntity.class);
            // 1 时间校验
            long time = System.currentTimeMillis();
            long ttl = seckillBeanActivityEntity.getEndTime().getTime() - time;
            long startTime = seckillBeanActivityEntity.getStartTime().getTime();
            long endTime = seckillBeanActivityEntity.getEndTime().getTime();
            if (time >= startTime && time <= endTime) {
                // 2 验证抢购份数是否合理
                if (seckillCount.intValue() <= seckillBeanActivityEntity.getSeckillLimit()) {
                    // 3 验证当前用户是否已经购买过。 幂等性; 如果只要秒杀成功，就去占位。  userId_SessionName
                    String key = SECKILL_BEAN_RECORD_CACHE_PREFIX + userId + "_" + seckillBeanActivityEntity.getSessionName();
                    // 4 自动过期
                    Boolean isSeckill = redisTemplate.opsForValue().setIfAbsent(key, seckillCount.toString(), ttl, TimeUnit.MILLISECONDS);
                    if (Boolean.TRUE.equals(isSeckill)) {
                        // 5 占位成功说明当前仙豆活动从来没有参与过
                        RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_BEAN_CACHE_PREFIX + seckillBeanActivityEntity.getSessionName());
                        boolean b = semaphore.tryAcquire(seckillCount.intValue());
                        if (b) {
                            log.info("SeckillBeanServiceImpl: seckillBean: 仙豆礼包领取成功");
                            // 6 抢购成功
                            BeanSeckillDto beanSeckillDto = BeanSeckillDto.builder()
                                    .userId(userId)
                                    .count(seckillCount * seckillBeanActivityEntity.getBeanCount())
                                    .build();
                            rabbitTemplate.convertAndSend("bean-event-exchange", "bean.seckill", beanSeckillDto);
                            return R.ok("仙豆礼包领取成功");
                        } else {
                            log.info("SeckillBeanServiceImpl: seckillBean: 仙豆已抢光啦，祝下一场好运");
                            return R.ok("仙豆已抢光啦，祝下一场好运");
                        }
                    } else {
                        log.info("SeckillBeanServiceImpl: seckillBean: 仙豆礼包无法重复领取");
                        return R.ok("仙豆礼包无法重复领取");
                    }

                } else {
                    log.info("SeckillBeanServiceImpl: seckillBean: 抢购份数非法");
                    return R.ok("抢购份数非法");
                }
            }
        }
        return R.ok("仙豆礼包领取失败，暂无活动");
    }


    @Override
    public void uploadSeckillBeanActivity(SeckillBeanActivityEntity seckillBeanActivityEntity) {
        if (seckillBeanActivityEntity != null) {
            // 缓存活动时间
            String sessionName = seckillBeanActivityEntity.getSessionName();
            String key = SECKILL_BEAN_SESSION_CACHE_PREFIX + sessionName;
            if (!Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
                redisTemplate.opsForValue().set(key, JSON.toJSONString(seckillBeanActivityEntity));
                redisTemplate.expireAt(key, seckillBeanActivityEntity.getEndTime());
            } else {
                log.info("SeckillBeanServiceImpl: uploadSeckillBeanActivity: 仙豆活动已在Redis中缓存");
            }
            // 缓存仙豆活动份数
            if (!Boolean.TRUE.equals(redisTemplate.hasKey(SECKILL_BEAN_CACHE_PREFIX + sessionName))) {
                // 使用仙豆份数作为信号量
                RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_BEAN_CACHE_PREFIX + sessionName);
                semaphore.trySetPermits(seckillBeanActivityEntity.getSeckillCount());
                // TODO semaphore过期时间设置待优化
                semaphore.expireAt(seckillBeanActivityEntity.getEndTime());
            } else {
                log.info("SeckillBeanServiceImpl: uploadSeckillBeanActivity: 仙豆活动的仙豆份数已在Redis中缓存");
            }
        }
    }


    @Override
    public SeckillBeanActivityVo listSeckillBeanActivity() {
        String sessionName = LocalDate.now() + "-" + LocalDateTime.now().getHour();
        String jsonStr = redisTemplate.opsForValue().get(SECKILL_BEAN_SESSION_CACHE_PREFIX + sessionName);
        SeckillBeanActivityVo seckillBeanActivityVo = null;
        if (!StringUtils.isEmpty(jsonStr)) {
            log.info("SeckillBeanServiceImpl: listSeckillBeanActivity: seckillBeanActivityEntity: {}", jsonStr);
            seckillBeanActivityVo = new SeckillBeanActivityVo();
            SeckillBeanActivityEntity seckillBeanActivityEntity = JSON.parseObject(jsonStr, SeckillBeanActivityEntity.class);
            BeanUtils.copyProperties(seckillBeanActivityEntity, seckillBeanActivityVo);
        }
        return seckillBeanActivityVo;
    }
}
